# coding=UTF-8
import numpy as np
import random
from common_model import Common
import sympy

# 描述核的类
class Core:

	# 初始化
	def __init__(self, block):
		# 核所在的块
		self.block = block
		block.core_list.append(self)
		block.type = Common.BLOCK_LIVE
		# 核的数据包括：
		self.place_block = block # 当前所处的块。由于虚空连接的存在，核可以位于自己所属块以外的块
		self.place = 0 # 核的坐标。如果核的坐标为-1，核凋亡
		self.vector = 0 # 核的向量
		# 初始化核的坐标与核的向量
		self.place = random.randint(0, pow(self.place_block.size, self.place_block.dimension) - 1)
		self.vector = random.randint(0, pow(self.place_block.size, self.place_block.dimension) - 1)

	def tick_tock(self, world):
		self.tick_tock_23(world)

	def tick_tock_23(self, world):
		# 刻印之灵
		# 【素】是代码、【息】是数据、【刻印】是编译规则、【核】是线程
		# ...
		# 【刻印】是固定长度的浮点数组
		# 总刻印数量 = 最大刻印长度 * 刻印功能数量
		# 对每一长度，每一刻印功能，有一个【刻印】
		# ...
		# 核的数据包括：
		# 每一个核拥有：【核的坐标】、【核的向量】（两者都为取值范围为[0, 块尺寸 ^ 块维度)的整数）
		# 【核的向量】实际意义为核指向的坐标，取值范围限制核永远不会超出块，块之间的通信依靠{通}实现，块的生长依靠{虚空连接}实现。
		# {通}是块感知、影响环境的手段。实现方式为：当块内指定坐标【息】、【素】被读取或写入，改为读取或写入块外指定坐标【息】、【素】。
		# {虚空连接}是块生长的手段。实现方式为：
		# 当【核的坐标】位于虚空连接起点，【核的坐标】改为位于另一块内的虚空连接终点。
		# 当读取、写入【息】、【素】的坐标位于虚空连接起点，改为读取、写入位于另一块内的虚空连接终点。
		# 【息】与【素】是浮点数，充斥所有格（即，长度为块尺寸 ^ 块维度的浮点数组）
		# ...
		# 刻印功能包括：
		# 0、更改核的向量（参数：起始坐标、目的坐标）
		#   【核的向量】 = 【核的向量】 + 目的坐标 - 起始坐标
		# 1、更改指定息（参数：读取坐标、写入坐标）
		#   写入坐标【息】 = 读取坐标【息】
		# 2、更改指定素（参数：读取坐标、写入坐标）
		#   写入坐标【素】 = 读取坐标【素】
		# 3、核的分裂（参数：起始坐标、目的坐标）
		#   新增核，新核的【核的坐标】 = 旧核的【核的坐标】，新核的【核的向量】 = 目的坐标 - 起始坐标
		# 4、核的凋亡
		#   删除核
		# 5、随机化指定息（参数：指定坐标、无用坐标）（避免息的值趋向相同）
		#   指定坐标【息】 = 随机浮点数
		# 6、随机化指定素（参数：指定坐标、无用坐标）
		#   指定坐标【素】 = 随机浮点数
		# 刻印的特性包括：
		# 1、刻印应当向最频繁被激活的样子转变
		# ...
		# 运行流程为：
		# 对生的所有核
		#   1、找到与该【核】长度相同且【核与刻印差异】最小的【刻印】，其中：
		#     【核经过的格】 = 从 【核的坐标】 到 【核的向量】 经过的格
		#     【核的长度】 = 【核经过的格】 的长度
		#     【核经过的素】 = 【核经过的格】内的素组成的浮点数组
		#     对每个与核同长度的【刻印】，【核与刻印差异】 = 【核经过的素】 到 【刻印】 的空间距离
		#   2、执行该【刻印】的功能，刻印参数由【核经过的息】与【核的坐标】计算得到，其中：
		#     【核经过的息】 = 【核经过的格】内的息组成的浮点数组
		#   3、该【刻印】向该【核经过的素】转变（刻印特性1）
		#   4、核移动，即：
		#     真的那个向量 = 【核的向量】 - 【核的坐标】
		#     【核的坐标】 = 【核的向量】
		#     【核的向量】 = (【核的向量】 + 真的那个向量 + 块尺寸) % (块尺寸)
		# 1、核的凋亡判定（控制核的密度，限制运算量）
		#   如果【核的向量】存在核，则位于【核的向量】的核凋亡（这一步同时清理长度为0的核）
		#   如果出现重叠（【核的坐标】、【核的向量】都相同）的核，只保留其中一个
		# 2、清理所有凋亡的核
		# 3、如果生不再拥有核，生变为无

		# 1、找到与该【核】长度相同且【核与刻印差异】最小的【刻印】
		# 将坐标拆分到各维度（单整数坐标无法表示符号为负的方向）
		place_divide = np.array(self.one_to_divide(self.place))
		vector_divide = np.array(self.one_to_divide(self.vector))
		real_vector = vector_divide - place_divide # 计算真的那个向量
		# 计算核经过的格（近似算法，降低运算量同时提供路径稳定性）
		sort_vector = [] # 按照各维度距离排序
		for i in range(len(real_vector)): # [[该维度步长, 该维度距离], ...]
			if real_vector[i] == 0: # 去除距离为0的维度
				continue
			sort_vector.append([self.sign(real_vector[i]) * pow(self.place_block.size, i), real_vector[i]]) 
		sort_vector = sorted(sort_vector, key = lambda x: abs(x[1]), reverse = True)
		# 循环维度，计算路径
		path = [] # 路径坐标
		path.append(self.place)
		while len(sort_vector) > 0:
			new_sort_vector = [] # 仍未到达的维度
			for i in range(len(sort_vector)):
				# 向当前维度前进1
				sign = self.sign(sort_vector[i][1])
				sort_vector[i][1] = sign * (abs(sort_vector[i][1]) - 1)
				path.append(path[-1] + sort_vector[i][0])
				# 若该维度仍未到达，下轮继续沿该维度前进
				if abs(sort_vector[i][1]) > 0:
					new_sort_vector.append(sort_vector[i])
			sort_vector = new_sort_vector
		# 读取核经过的息、素
		info = [] # 核经过的息
		ether = [] # 核经过的素
		for i in range(len(path)):
			info.append(self.place_block.grid_io(self.place_block, path[i], 0, 0))
			ether.append(self.place_block.grid_io(self.place_block, path[i], 2, 0))
		ether = np.array(ether)
		# 计算刻印差异
		path_len = len(path) # 核的长度
		scar_same_len = world.scar[path_len] # 与核相同长度的刻印集合
		dist = [] # 核经过的素到各刻印距离
		for i in range(len(scar_same_len)):
			dist.append(np.sum(np.square(ether - scar_same_len[i])))
		# 确定刻印类型
		dist = np.array(dist)
		the_scar_index = np.argmin(dist)
		# 计算刻印参数
		info_cut = [[], []] # 将息分为两半，分别计算两个参数
		for i in range(int(len(info) / 2)): # 如果长度是奇数，不使用最后一格
			info_cut[0].append(info[i * 2])
			info_cut[1].append(info[i * 2 + 1])
		arg = [[], []] # 参数以核的坐标为基础依照核经过的息偏移
		for i in range(2):
			for j in range(len(place_divide)):
				arg[i].append(place_divide[j])
		for i in range(2): # 循环补齐息，使息的数量为块维度的整数倍
			index = 0
			while len(info_cut[i]) % self.place_block.dimension != 0:
				info_cut[i].append(info_cut[i][index])
				index += 1
		# for i in range(2): # 根据息对参数进行偏移
		# 	for j in range(len(info_cut[i])):
		# 		if info_cut[i][j] > 0: # 参数是坐标，每一维的取值范围都是[0, 块尺寸)
		# 			arg[i][j % self.place_block.dimension] = (arg[i][j % self.place_block.dimension] + 1) % self.place_block.size
		# 		else:
		# 			arg[i][j % self.place_block.dimension] = (arg[i][j % self.place_block.dimension] - 1 + self.place_block.size) \
		# 			                                     % self.place_block.size
		# 根据息对参数进行偏移
		# 核的影响范围，也控制参数检测敏感度，由于位点最多块尺寸个，考虑符号有正负，最高敏感度为2.0 / 块尺寸
		core_scale = float(self.place_block.size) * 0.5 # 核的影响范围
		for i in range(2): # 根据息对参数进行偏移
			for j in range(len(info_cut[i])):
				arg[i][j % self.place_block.dimension] += info_cut[i][j] * core_scale
		for i in range(2): # 参数规范化
			for j in range(len(arg[i])):
				t = round(arg[i][j]) # 参数是整数
				while t < 0: # 参数需要大于0
					t += self.place_block.size
				t = t % self.place_block.size # 参数小于块尺寸
				arg[i][j] = t
		# 2、执行刻印功能
		arg_0 = self.divide_to_one(arg[0])
		arg_1 = self.divide_to_one(arg[1])
		type_num = world.scar_func_type_num
		grid_speed = 0.5 # 刻印更改息或素的速度
		if the_scar_index % type_num == 0:
			# 0、更改核的向量（参数：起始坐标、目的坐标）
			#   【核的向量】 = 【核的向量】 + 目的坐标 - 起始坐标
			new_vector_divide = vector_divide + arg[1] - arg[0]
			new_vector_divide = (new_vector_divide + self.place_block.size) % self.place_block.size # 坐标不得为负
			self.vector = self.divide_to_one(new_vector_divide)
		if the_scar_index % type_num == 1:
			# 1、更改指定息（参数：读取坐标、写入坐标）
			#   写入坐标【息】 = 读取坐标【息】
			data = self.place_block.grid_io(self.place_block, arg_0, 0, 0)
			# data_0 = self.place_block.grid_io(self.place_block, arg_0, 0, 0)
			# data_1 = self.place_block.grid_io(self.place_block, arg_1, 0, 0)
			# data = data_0 * grid_speed + data_1 * (1.0 - grid_speed) # 按速度更改
			self.place_block.grid_io(self.place_block, arg_1, 1, data)
			world.grid_io_log.append([self.place_block, the_scar_index % type_num, arg[0], arg[1]]) # 记录信息
		if the_scar_index % type_num == 2:
			# 2、更改指定素（参数：读取坐标、写入坐标）
			#   写入坐标【素】 = 读取坐标【素】
			data = self.place_block.grid_io(self.place_block, arg_0, 2, 0)
			# data_0 = self.place_block.grid_io(self.place_block, arg_0, 2, 0)
			# data_1 = self.place_block.grid_io(self.place_block, arg_1, 2, 0)
			# data = data_0 * grid_speed + data_1 * (1.0 - grid_speed) # 按速度更改
			self.place_block.grid_io(self.place_block, arg_1, 3, data)
			world.grid_io_log.append([self.place_block, the_scar_index % type_num, arg[0], arg[1]]) # 记录信息
		if the_scar_index % type_num == 3:
			# 3、核的分裂（参数：起始坐标、目的坐标）
			#   新增核，新核的【核的坐标】 = 旧核的【核的坐标】，新核的【核的向量】 = 【核的向量】 + 目的坐标 - 起始坐标
			core = Core(self.block)
			core.place_block = self.place_block
			core.place = self.place
			vector_divide = np.array(self.one_to_divide(self.vector)) # 以当前向量为基础
			new_vector_divide = vector_divide + arg[1] - arg[0]
			new_vector_divide = (new_vector_divide + self.place_block.size) % self.place_block.size # 坐标不得为负
			core.vector = self.divide_to_one(new_vector_divide)
		# if the_scar_index == 4:
		# 	# 4、核的凋亡
		# 	#   删除核
		# 	self.place = -1 # 如果核的坐标为-1，核凋亡，会在tick_tock结束后调用clean清除
		# 	self.vector = -1
		if the_scar_index % type_num == 5:
			# 5、随机化指定息（参数：指定坐标、无用坐标）（避免息的值趋向相同）
			#   指定坐标【息】 = 随机浮点数
			data = random.random() * 2.0 - 1.0 # 取值范围(-1, 1)
			self.place_block.grid_io(self.place_block, arg_0, 1, data)
			world.grid_io_log.append([self.place_block, the_scar_index % type_num, arg[0], arg[1]]) # 记录信息
		if the_scar_index % type_num == 6:
			# 6、随机化指定素（参数：指定坐标、无用坐标）
			#   指定坐标【素】 = 随机浮点数
			data = random.random() * 2.0 - 1.0 # 取值范围(-1, 1)
			self.place_block.grid_io(self.place_block, arg_0, 3, data)
			world.grid_io_log.append([self.place_block, the_scar_index % type_num, arg[0], arg[1]]) # 记录信息
		# 统计刻印激活频率
		world.scar_active[path_len][the_scar_index] += 1.0 # 统计刻印激活频率
		# 3、该【刻印】向该【核经过的素】转变（刻印特性1）
		the_scar = scar_same_len[the_scar_index] # 该刻印
		speed = pow(0.1, int(the_scar_index / type_num) + 1)
		ether = ether * (1.0 - speed) + the_scar * speed # 素列向刻印转变
		for i in range(len(path)): # 写回更改的素列
			self.place_block.grid_io(self.place_block, path[i], 1, ether[i])
		speed *= 0.5 # 刻印改变速度是素列改变速度的x倍 0.1
		# self.block.scar[path_len][the_scar_index] = the_scar * (1.0 - speed) + ether * speed # 刻印向素列转变
		# 4、核移动
		if self.place != -1: # 如果该核已经凋亡，不必再移动
			# 标记回路
			if self.place_block == world.brain_user: # 只统计神赐之脑拥有者的回路
				max_size = self.place_block.max_size
				index = self.place * max_size + self.vector
				# 标记回路
				if index in world.path: # 如果该路径已存在
					if world.time - world.path[index] < world.path_span: # 如果在路径持续时间以内
						world.cycle[index] = world.time # 记录环
				world.path[index] = world.time # 刷新时间
				# 记录调用
				if index in world.path_num: # 如果该路径已存在
					world.path_num[index][0] += 1 # 统计次数
				else:
					world.path_num[index] = [1, len(world.path_num)] # 新建
			# 将坐标拆分到各维度（单整数坐标无法表示符号为负的方向）
			place_divide = np.array(self.one_to_divide(self.place))
			vector_divide = np.array(self.one_to_divide(self.vector))
			real_vector = vector_divide - place_divide # 计算真的那个向量
			self.place = self.vector # 【核的坐标】 = 【核的向量】
			vector_divide = vector_divide + real_vector  # 【核的向量】 = (【核的向量】 + 真的那个向量 + 块尺寸) % (块尺寸)
			vector_divide = (vector_divide + self.place_block.size) % self.place_block.size # 坐标不得为负
			self.vector = self.divide_to_one(vector_divide)
			# 判定傀儡回路
			#  0  1  2  3  4  5  6  7
			#  8  9 10 11 12 13 14 15
			# 16 17 18 19 20(21)22 23
			# 24 25 26 27 28 29 30 31
			# 32 33 34 35 36 37 38 39
			# 40 41(42)43 44 45 46 47
			# 48 49 50 51 52 53 54 55
			# 56 57 58 59 60 61 62 63
			# 傀儡回路经过点位需要对称设计，以降低点位位置本身造成的激活概率差异
			tool_cycle_strength = 10 # 傀儡回路强度
			if self.place == 42 + 64 * 3:
				world.tool_cycle_all += 1
				if self.block.time < 500: # 老实说，代码写成这样有点丑陋
					self.block.time += tool_cycle_strength
				else:
					self.block.time -= tool_cycle_strength
					world.tool_cycle_num += 1
			if self.place == 21 + 64 * 3:
				world.tool_cycle_all += 1
				if self.block.time < 500:
					self.block.time -= tool_cycle_strength
					world.tool_cycle_num += 1
				else:
					self.block.time += tool_cycle_strength
			# 判定核是否通过虚空连接
			# 虚空连接: [[0: 起点位置, 1: 目标块, 2: 终点位置], ...]
			connect = self.place_block.connect
			for i in range(len(connect)):
				if connect[i][0] == self.place: # 符合虚空连接
					# 核更改为向虚空连接指向的块
					self.place_block = connect[i][1]
					self.place = connect[i][2]

	def sign(self, x):
		if x >= 0:
			return 1
		else:
			return -1

	def one_to_divide(self, vector_int):
		# 将向量拆分到各维度
		limit = pow(self.place_block.size, self.place_block.dimension - 1)
		vector_divide = []
		for i in range(self.place_block.dimension):
			vector_divide.insert(0, int(vector_int / limit))
			vector_int = vector_int % int(limit)
			limit = limit / self.place_block.size
		return vector_divide

	def divide_to_one(self, vector_divide):
		# 合并向量
		limit = 1
		vector_int = 0
		for i in range(len(vector_divide)):
			vector_int += limit * vector_divide[i]
			limit *= self.place_block.size
		return vector_int
